#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <cerrno>

#include <string>
#include <fstream>
#include <vector>

#include "SVMPredictor.h"

using namespace std;

SVMPredictor::SVMPredictor() {
	this->info = &printf;

	this->predictProbability = kDefaultPredictProbability;

	modelLoaded = false;
}

SVMPredictor::~SVMPredictor() {
	destroyModel();
}

bool SVMPredictor::loadModel(const char* modelFile) {
	if (modelLoaded) {
		//model loaded before
		destroyModel();
	}

	if ((model = svm_load_model(modelFile)) == 0) {
		fprintf(stderr, "can't open model file %s\n", modelFile);
		return false;
	}

	modelLoaded = true;
	return true;
}

void SVMPredictor::destroyModel() {
	if (modelLoaded) {
		svm_free_and_destroy_model(&model);
		modelLoaded = false;
	}
}

bool SVMPredictor::isPredictProbability() {
	return predictProbability;
}

void SVMPredictor::setPredictProbability(bool isPredict) {
	this->predictProbability = isPredict;
}

void SVMPredictor::setQuietMode(bool isQuiet) {
	if (isQuiet) {
		this->info = &print_null;
	} else {
		this->info = &printf;
	}
}

bool SVMPredictor::parseLineData(double& targetLabel, vector<svm_node>& data, string& line, int currentLineNumber) {
	char *label, *endptr;

	vector<char> chars(line.c_str(), line.c_str() + line.size() + 1u);
	char *tmpLine = &chars[0];
	label = strtok(tmpLine, " \t\n");
	if (label == NULL) { // empty line
		fprintf(stderr, "Wrong input format at line %d\n", currentLineNumber);
		return false;
	}

	targetLabel = strtod(label, &endptr);
	if (endptr == label || *endptr != '\0') {
		fprintf(stderr, "Wrong input format at line %d\n", currentLineNumber);
		return false;
	}

	int inst_max_index = -1; // strtol gives 0 if wrong format, and precomputed kernel has <index> start from 0
	while (true) {
		svm_node aNode;

		char *idx, *val;
		idx = strtok(NULL, ":");
		val = strtok(NULL, " \t");

		if (val == NULL)
			break;
		errno = 0;
		aNode.index = (int) strtol(idx, &endptr, 10);
		if (endptr == idx || errno != 0 || *endptr != '\0' || aNode.index <= inst_max_index) {
			fprintf(stderr, "Wrong input format at line %d\n", currentLineNumber);
			return false;
		} else
			inst_max_index = aNode.index;

		errno = 0;
		aNode.value = strtod(val, &endptr);
		if (endptr == val || errno != 0 || (*endptr != '\0' && !isspace(*endptr))) {
			fprintf(stderr, "Wrong input format at line %d\n", currentLineNumber);
			return false;
		}

		data.push_back(aNode);
	}
	
	return true;
}

void SVMPredictor::predictFile(const char* input, const char *outputFile) {
	if (!modelLoaded) {
		fprintf(stderr, "NO model loaded\n");
		return;
	}

	//model loaded
	if (predictProbability) {
		if (svm_check_probability_model(model) == 0) {
			fprintf(stderr, "Model does not support probabiliy estimates\n");
			return;
		}
	} else {
		if (svm_check_probability_model(model) != 0)
			info("Model supports probability estimates, but disabled in prediction.\n");
	}

	FILE *output = fopen(outputFile, "w");
	if (output == NULL) {
		fprintf(stderr, "can't open output file %s\n", outputFile);
		return;
	}

	int svmType = svm_get_svm_type(model);
	int numberClass = svm_get_nr_class(model);

	if (predictProbability) {
		if (svmType == NU_SVR || svmType == EPSILON_SVR)
			info("Prob. model for test data: target value = predicted value + z,\nz: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma=%g\n", svm_get_svr_probability(model));
		else {
			int *labels = (int *) malloc(numberClass * sizeof (int));
			svm_get_labels(model, labels);
			fprintf(output, "labels");
			for (int i = 0; i < numberClass; i++)
				fprintf(output, " %d", labels[i]);
			fprintf(output, "\n");
			free(labels);
		}
	}

	ifstream ifs;
	ifs.open(input);
	if (!ifs.is_open()) {
		fprintf(stderr, "can't open file %s\n", input);
		return;
	}

	int correct = 0;
	int total = 0;
	double error = 0;
	double sump = 0, sumt = 0, sumpp = 0, sumtt = 0, sumpt = 0;
	vector<double> probEstimates;

	string line;
	while (getline(ifs, line)) {
		vector<svm_node> data;
		double targetLabel;
		if (!parseLineData(targetLabel, data, line, total + 1)) {
			return;
		}

		probEstimates.clear();
		double predictedLabel = predictData(data, probEstimates);
		
		//print output for an input line
		if (predictProbability && (svmType == C_SVC || svmType == NU_SVC)) {
			fprintf(output, "%g", predictedLabel);
			for (int i = 0; i < numberClass; i++)
				fprintf(output, " %g", probEstimates[i]);
			fprintf(output, "\n");
		} else {
			fprintf(output, "%g\n", predictedLabel);
		}

		//collect results
		if (predictedLabel == targetLabel)
			++correct;
		error += (predictedLabel - targetLabel)*(predictedLabel - targetLabel);
		sump += predictedLabel;
		sumt += targetLabel;
		sumpp += predictedLabel*predictedLabel;
		sumtt += targetLabel*targetLabel;
		sumpt += predictedLabel*targetLabel;
		++total;
	}
	
	//print summary
	if (svmType == NU_SVR || svmType == EPSILON_SVR) {
		info("Mean squared error = %g (regression)\n", error / total);
		info("Squared correlation coefficient = %g (regression)\n",
				((total * sumpt - sump * sumt)*(total * sumpt - sump * sumt)) /
				((total * sumpp - sump * sump)*(total * sumtt - sumt * sumt))
				);
	} else
		info("Accuracy = %g%% (%d/%d) (classification)\n",
			(double) correct / total * 100, correct, total);
}

double SVMPredictor::predictData(const vector<svm_node>& data, vector<double>& probabilityEstimates) {
	//convert data to array
	struct svm_node *x;
	x = (struct svm_node *) malloc((data.size() + 1) * sizeof (struct svm_node));
	for (int i = 0; i < data.size(); i++) {
		x[i].index = data[i].index;
		x[i].value = data[i].value;
	}
	x[data.size()].index = -1;

	//PREDICT
	int svmType = svm_get_svm_type(model);
	double predictedLabel;

	if (predictProbability && (svmType == C_SVC || svmType == NU_SVC)) {
		int numberClass = svm_get_nr_class(model);
		double *probEstimates = NULL;
		probEstimates = (double *) malloc(numberClass * sizeof (double));

		predictedLabel = svm_predict_probability(model, x, probEstimates);

		probabilityEstimates.clear();
		for (int i = 0; i < numberClass; i++) {
			probabilityEstimates.push_back(probEstimates[i]);
		}

		free(probEstimates);
	} else {
		predictedLabel = svm_predict(model, x);
	}

	free(x);

	return predictedLabel;
}